import json
import os
import random
import re
import subprocess
from time import sleep

import requests

from user_agent_list import random_ua


class Bilibili(object):
    def __init__(self, url=None, *args, **kwargs):
        super().__init__()
        self.num = 0
        # 目标视频主页
        self.url = url
        self.headers = {
            "accept": "*/*",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
            "cache-control": "no-cache",
            "origin": "https://www.bilibili.com",
            "pragma": "no-cache",
            "host": 'www.bilibili.com',
            "user-agent": random_ua()
        }
        # 视频目录名称
        self.name = None

    # 解析initial_state
    def get_initial_state(self):
        response_text = self.start_requests(url=self.url, headers=self.headers).text
        reg1 = r'</script><script>window.__INITIAL_STATE__=(.*?);'
        detail = re.findall(reg1, response_text, re.S)
        # print(detail)
        # initial_state包含所有剧集的id，badge(可判断是否需要会员)，aid，bvid(可以请求到播放页面数据)，cid，longTitle等信息
        if not detail:
            raise ValueError('页面解析错误')
        initial_state = json.loads(detail[0])
        # 此处为番剧信息
        if 'epList' in initial_state:
            reg2 = r'meta name="keywords" content="(.*?)"'
            self.name = re.findall(reg2, response_text, re.S)[0].strip()
            print(self.name)

            epList = initial_state['epList']
            # 遍历所有epList获取所有视频信息
            for ep in epList:
                # print(ep)
                dic = {}
                # 判断视频是否需要会员
                if ep['badge'] != '会员':  # 非会员则继续
                    dic['bvid'] = str(ep['bvid'])
                    dic['titleFormat'] = ep['titleFormat']
                    dic['longTitle'] = ep['longTitle']
                    self.num += 1
                    self.get_play_info(dic=dic)
                else:
                    continue
            print('已全部抓取完成')
        else:  # 此处为其他视频
            dic = {}
            videoData = initial_state['videoData']
            self.name = videoData['tname']
            dic['bvid'] = videoData['bvid']
            title = videoData['title']
            dic['title'] = title
            self.num += 1
            self.get_play_info(dic=dic)

    # 解析get_play_info
    def get_play_info(self, *args, **kwargs):
        dic = kwargs['dic']
        bvid = dic['bvid']
        # 番剧一般有多个剧集，这里需要拼接url，
        url = 'https://www.bilibili.com/video/' + bvid
        dic['url'] = url

        response = self.start_requests(url=url, headers=self.headers)
        response_text = response.text
        # print(response_text)
        reg = r'<script>window.*?__playinfo__=(.*?)</script>'
        detail = re.findall(reg, response_text, re.S)
        # print(detail)
        # playinfo包含当前url请求到的视频与音频链接以及流媒体文件长度
        playinfo = json.loads(detail[0])
        # 视频地址
        video_url = playinfo['data']['dash']['video'][0]['backupUrl'][-1]
        # print(video_url)
        dic['video_url'] = video_url
        # 要发起请求的视频长度,要用作请求头中的Range参数,只在番剧中可用，注释掉
        # if 'size' in playinfo:
        #     video_range_size = playinfo['data']['dash']['video'][0]['size']
        #     # print(video_range_size)
        #     dic['video_range_size'] = video_range_size
        # else:
        #     dic['video_range_size']=0
        # 音频地址
        audio_url = playinfo['data']['dash']['audio'][0]['backupUrl'][-1]
        # print(audio_url)
        dic['audio_url'] = audio_url
        # 要发起请求的音频长度,要用作请求头中的Range参数,只在番剧中可用，注释掉
        # if 'size' in playinfo:
        #     audio_range_size = playinfo['data']['dash']['audio'][0]['size']
        #     # print(video_range_size)
        #     dic['video_range_size'] = audio_range_size
        # else:
        #     dic['video_range_size']=0
        self.parse_data(dic=dic)

    # 解析音频、视频文件
    def parse_data(self, dic):
        file_path = self.name
        if not os.path.exists(file_path):
            os.mkdir(file_path)
        # 此处url作为请求头参数
        url = dic['url']

        video_url = dic['video_url']
        if 'titleFormat' in dic:
            video_filename = dic['titleFormat'] + dic['longTitle'] + '.m4s'
            audio_filename = dic['titleFormat'] + dic['longTitle'] + '.mp3'
        else:
            video_filename = dic['title'] + '.m4s'
            audio_filename = dic['title'] + '.mp3'

        # 要发起请求的视频url
        f1 = open(file_path + '/' + video_filename, 'wb')

        main_headers = {
            'Accept': '*/*',
            'accept-encoding': 'identity',
            'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'Accept-Encoding': 'gzip, deflate, br',
            'cache-control': 'no-cache',
            'Origin': 'https://www.bilibili.com',
            'pragma': 'no-cache',
            'Range': 'bytes=0-299',
            'Referer': url,
            'User-Agent': random_ua(),
            'Connection': 'keep-alive',
        }
        # 先向video_url发起一次请求可以从响应头中得到Content-Range参数，里面包含了视频的大小数据
        response = self.start_requests(url=video_url, headers=main_headers, stream=True)
        video_range_size = int(response.headers['Content-Range'].split('/')[-1])
        headers = {
            'Accept': '*/*',
            'accept-encoding': 'identity',
            'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'Accept-Encoding': 'gzip, deflate, br',
            'cache-control': 'no-cache',
            'Origin': 'https://www.bilibili.com',
            'pragma': 'no-cache',
            'Range': 'bytes=0-%s' % video_range_size,
            'Referer': url,
            'User-Agent': random_ua(),
            'Connection': 'keep-alive',
        }
        print('正在抓取:' + video_filename)
        # 请求视频数据
        response = self.start_requests(url=video_url, headers=headers, stream=True)
        for content in response.iter_content(chunk_size=1024):
            # print(content)
            f1.write(content)
        f1.close()
        print(video_filename + '已抓取完成')

        # # 要发起请求的音频url
        audio_url = dic['audio_url']
        # 先向audio_url发起一次请求可以从响应头中得到Content-Range参数，里面包含了音频的大小数据
        response = self.start_requests(url=audio_url, headers=main_headers, stream=True)
        audio_range_size = int(response.headers['Content-Range'].split('/')[-1])
        f2 = open(file_path + '/' + audio_filename, mode='wb')
        # 音频数据无法一次性全部请求到，一次性请求只能拿到一半数据
        print('正在抓取:' + audio_filename)
        for i in [[0, int(audio_range_size / 2)], [int(audio_range_size / 2) + 1, audio_range_size]]:
            headers = {
                'Accept': '*/*',
                'accept-encoding': 'identity',
                'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
                'Accept-Encoding': 'gzip, deflate, br',
                'cache-control': 'no-cache',
                'Origin': 'https://www.bilibili.com',
                'pragma': 'no-cache',
                'Range': 'bytes=%s-%s' % (i[0], i[1]),
                'Referer': url,
                'User-Agent': random_ua(),
                'Connection': 'keep-alive',
            }
            response = self.start_requests(url=audio_url, headers=headers, stream=True)
            for content in response.iter_content(chunk_size=1024):
                # print(content)
                f2.write(content)
            sleep(random.random() * 2)

        f2.close()
        print(audio_filename + '已抓取完成')

        # 电脑性能有限不做合并操作
        # return self.video_add_audio(video_filename, audio_filename)

    # 发起请求
    @staticmethod
    def start_requests(url, headers, stream=None):
        response = requests.session().get(url=url, headers=headers, stream=stream)
        return response

    # 合并视频音频文件
    @staticmethod
    def video_add_audio(video_filename, audio_filename):
        """
        视频添加音频
        :param video_filename: 视频文件的路径
        :param audio_filename: 音频文件的路径
        :return:
        """
        try:
            outfile_name = video_filename.split('.')[0] + '.mp4'
            subprocess.call('ffmpeg -i ' + video_filename
                            + ' -i ' + audio_filename + ' -strict -2 -f mp4 '
                            + outfile_name, shell=True)
            print(outfile_name + '解析完成')
            os.remove(video_filename)
            os.remove(audio_filename)
        except Exception as e:
            print(e)
        return

    def main(self):
        print('正在解析：' + self.url)
        self.get_initial_state()
